#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "raylib.h"
#include "../engine/include/shape.h"
#include "../engine/include/text.h"

typedef enum GAME_STATE {
    GAME_STATE_PLAYING,   // 可操作
    GAME_STATE_CELL_MOVE, // 格子移动
    GAME_STATE_NEW_CELL,  // 产生新格子,
    GAME_STATE_FAIL,      // 失败
    GAME_STATE_WIN,       // 成功
} GAME_STATE;

typedef enum CELL_STATE {
    CELL_STATE_NORMAL,  // 正常状态
    CELL_STATE_MERGING, // 合并状态
    CELL_STATE_MOVE,    // 移动状态
} CELL_STATE;

typedef enum MOVE_DIRECTION {
    MOVE_DIRECTION_UP,
    MOVE_DIRECTION_DOWN,
    MOVE_DIRECTION_LEFT,
    MOVE_DIRECTION_RIGHT,
} MOVE_DIRECTION;

// structs
typedef struct PosRowCol {
    int row;
    int col;
} PosRowCol;

GAME_STATE gameState = GAME_STATE_PLAYING;

// constants
const int ROWS = 4;
const int COLS = 4;
const int CELL_WIDTH = 120;
const int CELL_HEIGHT = 120;
const int CELL_SPACE = 11;
const float MOVE_TIME = .2f;

// 数字显示的配置
typedef struct NumConfig {
    int fontSize; // 字体大小
    Color fontColor; // 字体颜色
    Color bgColor; // 背景颜色
    char* numStr;   // 数字字符串
} NumConfig;

const NumConfig numsCfg[11] = {
    {60, (Color){119,110,101,255}, (Color){238, 228, 218,255}, "2"},
    {60, (Color){119,110,101,255}, (Color){237, 224, 200,255}, "4"},
    {60, (Color){249,246,242,255}, (Color){242, 177, 121,255}, "8"},
    {60, (Color){249,246,242,255}, (Color){245, 149, 99,255}, "16"},
    {60, (Color){249,246,242,255}, (Color){246, 124, 95,255}, "32"},
    {60, (Color){249,246,242,255}, (Color){246, 94, 59,255}, "64"},
    {60, (Color){249,246,242,255}, (Color){237, 207, 114,255}, "128"},
    {60, (Color){249,246,242,255}, (Color){237, 204, 97,255}, "256"},
    {60, (Color){249,246,242,255}, (Color){237, 200, 80,255}, "512"},
    {40, (Color){249,246,242,255}, (Color){237, 197, 63,255}, "1024"},
    {40, (Color){249,246,242,255}, (Color){237, 194, 46,255}, "2048"},
};

// 格子
typedef struct Cell {
    int num;  // 数字
    int row; // 行
    int col; // 列
    float moveSpeed; // 移动速度
    Vector2 pos;    // 位置
    CELL_STATE state; // 状态
    MOVE_DIRECTION direction; // 移动方向
    struct Cell *fromCells[2]; // 来源格子
} Cell;
// 网格
Cell *grid[ROWS][COLS];

// functions
void init();
void initGame();

void checkKeyDown();

void drawBg();
void driveCells(float dt);
void drawCells();

void checkFail();
void drawFail();
void checkWin();
void drawWin();

void checkAddNewCells();
void addNewCells(int num);

void moveLeft();
void moveUp();
void moveDown();
void moveRight();

Vector2 getPosOfRowCol(int row, int col);
PosRowCol getRowColOfPos(Vector2 pos);

PosRowCol getOneRandomFreePrc();

int main() {
    init();
    while (!WindowShouldClose()) {
        checkKeyDown();
        BeginDrawing();
            drawBg();
            driveCells(GetFrameTime());
            drawCells();
            drawFail();
            drawWin();
        EndDrawing();
    }

    CloseWindow();
    return 0;
}

void init() {
    SetConfigFlags(FLAG_WINDOW_HIGHDPI);
    int width = CELL_SPACE * (ROWS + 1) + CELL_WIDTH * ROWS;
    int height = CELL_SPACE * (COLS + 1) + CELL_HEIGHT * COLS;
    InitWindow(width, height, "2048 Game");
    SetTargetFPS(60);
    initGame();
}

void initGame() {
    gameState = GAME_STATE_PLAYING;

    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            grid[row][col] = NULL;
        }
    }

    // Cell *cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 2; cell->row = 0; cell->col = 0;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 4; cell->row = 0; cell->col = 1;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 8; cell->row = 0; cell->col = 2;
    // grid[cell->row][cell->col] = cell;


    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 16; cell->row = 0; cell->col = 3;
    // grid[cell->row][cell->col] = cell;


    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 32; cell->row = 1; cell->col = 0;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 64; cell->row = 1; cell->col = 1;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 128; cell->row = 1; cell->col = 2;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 256; cell->row = 1; cell->col = 3;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 512; cell->row = 2; cell->col = 0;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 1024; cell->row = 2; cell->col = 1;
    // grid[cell->row][cell->col] = cell;

    // cell = (Cell *)malloc(sizeof(Cell));
    // cell->num = 2048; cell->row = 2; cell->col = 2;
    // grid[cell->row][cell->col] = cell;


    addNewCells(2);
}

void addNewCells(int num) {
    for (int i = 0; i < num; i++) {
        PosRowCol rcPos = getOneRandomFreePrc();
        if (rcPos.row == -1) continue;

        Cell *cell = (Cell *)malloc(sizeof(Cell));
        cell->num = 2;
        if (GetRandomValue(0, 3) == 0) cell->num = 4;
        cell->row = rcPos.row;
        cell->col = rcPos.col;
        grid[rcPos.row][rcPos.col] = cell;
    }
}

void checkKeyDown() {
    if (IsKeyPressed(KEY_R)) initGame();

    if (gameState != GAME_STATE_PLAYING) return;

    if (IsKeyPressed(KEY_UP)) moveUp();
    else if (IsKeyPressed(KEY_DOWN)) moveDown();
    else if (IsKeyPressed(KEY_LEFT)) moveLeft();
    else if (IsKeyPressed(KEY_RIGHT)) moveRight();
}

void drawBg() {
    ClearBackground((Color){153, 139, 126, 255});
    for (int row = 0; row != ROWS; row++) {
        for (int col = 0; col != COLS; col++) {
            Vector2 pos = getPosOfRowCol(row, col);
            drawRectangleRoundedCenter(
                pos.x, 
                pos.y, 
                CELL_WIDTH, 
                CELL_HEIGHT, 
                0.2f, 10, 
                (Color){186, 173, 154, 255});
        }
    }
}

void driveCells(float dt) {
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Cell *cell = grid[row][col];
            if (cell == NULL) continue;
            Vector2 pos = getPosOfRowCol(row, col);

            for (int i = 0; i < 2; i++) {
                Cell *fromCell = cell->fromCells[i];
                if (fromCell == NULL) continue;

                if (fromCell->direction == MOVE_DIRECTION_UP) {
                    fromCell->pos.y -= fromCell->moveSpeed * dt;
                    if (fromCell->pos.y <= pos.y) {
                        cell->num += fromCell->num;
                        cell->fromCells[i] = NULL;
                        free(fromCell);

                        checkWin();
                        checkAddNewCells();
                        checkFail();
                    }
                } else if (fromCell->direction == MOVE_DIRECTION_DOWN) {
                    fromCell->pos.y += fromCell->moveSpeed * dt;
                    if (fromCell->pos.y >= pos.y) {
                        cell->num += fromCell->num;
                        cell->fromCells[i] = NULL;
                        free(fromCell);

                        checkWin();
                        checkAddNewCells();
                        checkFail();
                    }
                } else if (fromCell->direction == MOVE_DIRECTION_LEFT) {
                    fromCell->pos.x -= fromCell->moveSpeed * dt;
                    if (fromCell->pos.x <= pos.x) {
                        cell->num += fromCell->num;
                        cell->fromCells[i] = NULL;
                        free(fromCell);

                        checkWin();
                        checkAddNewCells();
                        checkFail();
                    }
                } else if (fromCell->direction == MOVE_DIRECTION_RIGHT) {
                    fromCell->pos.x += fromCell->moveSpeed * dt;
                    if (fromCell->pos.x >= pos.x)
                    {
                        cell->num += fromCell->num;
                        cell->fromCells[i] = NULL;
                        free(fromCell);

                        checkWin();
                        checkAddNewCells();
                        checkFail();
                    }
                }
            }
        }
    }
}

void checkAddNewCells() {
    if (gameState == GAME_STATE_WIN) return;
    bool allNormal = true;
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Cell *cell = grid[row][col];
            if (cell == NULL) continue;
                
            if (cell->fromCells[0] != NULL || cell->fromCells[1] != NULL) {
                allNormal = false;
                break;
            }
        }
    }

    if (allNormal) {
        gameState = GAME_STATE_PLAYING;
        addNewCells(2);
    }
}

void drawCells() {
    for (int row = 0; row != ROWS; row++) {
        for (int col = 0; col != COLS; col++) {
            Cell *cell = grid[row][col];
            if (cell == NULL) continue;

            if (cell->num > 0) {
                Vector2 pos = getPosOfRowCol(row, col);
                int idx = log2(cell->num) - 1;
                drawRectangleRoundedCenter(pos.x, pos.y, CELL_WIDTH, CELL_HEIGHT, 0.2f, 10, numsCfg[idx].bgColor);
                drawTextCenter(numsCfg[idx].numStr, pos.x, pos.y, numsCfg[idx].fontSize, numsCfg[idx].fontColor);
            }

            for (int i = 0; i < 2; i++) {
                Cell *fromCell = cell->fromCells[i];
                if (fromCell == NULL) continue;
                int idx = log2(fromCell->num) - 1;
                drawRectangleRoundedCenter(fromCell->pos.x, fromCell->pos.y, CELL_WIDTH, CELL_HEIGHT, 0.2f, 10, numsCfg[idx].bgColor);
                drawTextCenter(numsCfg[idx].numStr, fromCell->pos.x, fromCell->pos.y, numsCfg[idx].fontSize, numsCfg[idx].fontColor);
            }
        }
    }
}

Vector2 getPosOfRowCol(int row, int col) {
    Vector2 startPos = (Vector2){0, 0};
    int x = startPos.x + (col + 1) * CELL_SPACE + col * CELL_WIDTH + CELL_WIDTH * 0.5;
    int y = startPos.y + (row + 1) * CELL_SPACE + row * CELL_HEIGHT + CELL_HEIGHT * 0.5;
    return (Vector2){x, y};
}

PosRowCol getRowColOfPos(Vector2 pos) {
    PosRowCol rcPos = {-1, -1};
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Vector2 posOfCell = getPosOfRowCol(row, col);
            if (pos.x >= posOfCell.x && pos.x <= posOfCell.x + CELL_WIDTH && pos.y >= posOfCell.y && pos.y <= posOfCell.y + CELL_HEIGHT) {
                rcPos.row = row;
                rcPos.col = col;
                break;
            }
        }
    }
    return rcPos;
}

void checkFail() {
    bool isFail = true;
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Cell *cell = grid[row][col];
            if (cell == NULL) {
                isFail = false;
                goto checkover;
            }

            if (row > 0) {
                Cell *upCell = grid[row - 1][col];
                if (upCell != NULL && upCell->num == cell->num) {
                    isFail = false;
                    goto checkover;
                }
            }

            if (row < ROWS - 1) {
                Cell *downCell = grid[row + 1][col];
                if (downCell != NULL && downCell->num == cell->num) {
                    isFail = false;
                    goto checkover;
                }
            }

            if (col > 0) {
                Cell *leftCell = grid[row][col - 1];
                if (leftCell != NULL && leftCell->num == cell->num) {
                    isFail = false;
                    goto checkover;
                }
            }

            if (col < COLS - 1) {
                Cell *rightCell = grid[row][col + 1];
                if (rightCell != NULL && rightCell->num == cell->num) {
                    isFail = false;
                    goto checkover;
                }
            }
        }
    }
checkover:
    if (isFail) gameState = GAME_STATE_FAIL;
}

void drawFail() {
    if (gameState != GAME_STATE_FAIL) return;

    int width = CELL_SPACE * (ROWS + 1) + CELL_WIDTH * ROWS;
    int height = CELL_SPACE * (COLS + 1) + CELL_HEIGHT * COLS;
    drawRectangleCenter(width / 2, height / 2, width, height, (Color){255, 255, 255, 120});
    drawTextCenter("You Lose!", width / 2, height / 2 - 60, 80, (Color){119,110,101,255});
    drawTextCenter("Press R to Restart", width / 2, height / 2 + 20, 20, (Color){119,110,101,255});
}

void checkWin() {
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Cell *cell = grid[row][col];
            if (cell != NULL && cell->num == 2048) {
                gameState = GAME_STATE_WIN;
                printf("You Win!\n");
                return;
            }
        }
    }
}

void drawWin() {
    if (gameState != GAME_STATE_WIN) return;

    int width = CELL_SPACE * (ROWS + 1) + CELL_WIDTH * ROWS;
    int height = CELL_SPACE * (COLS + 1) + CELL_HEIGHT * COLS;
    drawRectangleCenter(width / 2, height / 2, width, height, (Color){255, 255, 255, 120});
    drawTextCenter("You Win!", width / 2, height / 2 - 60, 80, (Color){119,110,101,255});
    drawTextCenter("Press R to Restart", width / 2, height / 2 + 20, 20, (Color){119,110,101,255});
}

PosRowCol getOneRandomFreePrc() {
    PosRowCol rcPos = {-1, -1};
    PosRowCol freePoses[ROWS * COLS];
    int freePosesCount = 0;
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            if (grid[row][col] == NULL) {
                freePoses[freePosesCount].row = row;
                freePoses[freePosesCount].col = col;
                freePosesCount++;
            }
        }
    }

    if (freePosesCount > 0) {
        int index = GetRandomValue(0, freePosesCount - 1);
        rcPos.row = freePoses[index].row;
        rcPos.col = freePoses[index].col;
    }
    return rcPos;
}

Cell *getDownCell(int row, int col) {
    while (++row < ROWS) {
        if (grid[row][col] != NULL) return grid[row][col];
    }
    return NULL;
}

void moveUp() {
    for (int col = 0; col < COLS; col++) {
        int row = 0;
        while (row < ROWS) {
            Cell *cell = grid[row][col];
            if (cell == NULL) {
                // 当前是空的 向下找一个不空的格子移动到这里
                Cell *downCell = getDownCell(row, col);
                if (downCell == NULL) break;

                // 新建个格子 放在当前位置 让下面的格子移动过来
                Cell *newCell = (Cell *)malloc(sizeof(Cell));
                newCell->num = 0;
                newCell->row = row;
                newCell->col = col;
                newCell->fromCells[0] = downCell;
                grid[row][col] = newCell;

                grid[downCell->row][downCell->col] = NULL;
                downCell->direction = MOVE_DIRECTION_UP;
                downCell->pos = getPosOfRowCol(downCell->row, downCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                downCell->moveSpeed = (downCell->pos.y - dest.y) / MOVE_TIME;

                cell = newCell;

                gameState = GAME_STATE_CELL_MOVE;
            }

            Cell *downCell = getDownCell(row, col);
            if (downCell == NULL) break;

            // 下面的数字跟当前数字一样
            int cellNum = cell->num;
            if (cell->fromCells[0]) {
                cellNum += cell->fromCells[0]->num;
            }
            if (cell->fromCells[1]) {
                cellNum += cell->fromCells[1]->num;
            }
            if (cellNum == downCell->num) {
                grid[downCell->row][downCell->col] = NULL;
                downCell->direction = MOVE_DIRECTION_UP;
                downCell->pos = getPosOfRowCol(downCell->row, downCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                downCell->moveSpeed = (downCell->pos.y - dest.y) / MOVE_TIME;
                if (cell->fromCells[0] == NULL) {
                    cell->fromCells[0] = downCell;
                } else {
                    cell->fromCells[1] = downCell;
                }

                gameState = GAME_STATE_CELL_MOVE;
            }
            row++;
        }
    }
}

Cell *getUpCell(int row, int col) {
    while (--row >= 0) {
        if (grid[row][col] != NULL) return grid[row][col];
    }
    return NULL;
}

void moveDown() {
    for (int col = 0; col < COLS; col++) {
        int row = ROWS - 1;
        while (row >= 0) {
            Cell *cell = grid[row][col];
            if (cell == NULL) {
                // 当前是空的 向下找一个不空的格子移动到这里
                Cell *upCell = getUpCell(row, col);
                if (upCell == NULL) break;

                // 新建个格子 放在当前位置 让下面的格子移动过来
                Cell *newCell = (Cell *)malloc(sizeof(Cell));
                newCell->num = 0;
                newCell->row = row;
                newCell->col = col;
                newCell->fromCells[0] = upCell;
                grid[row][col] = newCell;

                grid[upCell->row][upCell->col] = NULL;
                upCell->direction = MOVE_DIRECTION_DOWN;
                upCell->pos = getPosOfRowCol(upCell->row, upCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                upCell->moveSpeed = (dest.y - upCell->pos.y) / MOVE_TIME;

                cell = newCell;

                gameState = GAME_STATE_CELL_MOVE;
            }

            Cell *upCell = getUpCell(row, col);
            if (upCell == NULL) break;

            // 上面的数字跟当前数字一样
            int cellNum = cell->num;
            if (cell->fromCells[0]) {
                cellNum += cell->fromCells[0]->num;
            }
            if (cell->fromCells[1]) {
                cellNum += cell->fromCells[1]->num;
            }
            if (cellNum == upCell->num) {
                grid[upCell->row][upCell->col] = NULL;
                upCell->direction = MOVE_DIRECTION_DOWN;
                upCell->pos = getPosOfRowCol(upCell->row, upCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                upCell->moveSpeed = (dest.y - upCell->pos.y) / MOVE_TIME;
                if (cell->fromCells[0] == NULL) {
                    cell->fromCells[0] = upCell;
                } else {
                    cell->fromCells[1] = upCell;
                }
                gameState = GAME_STATE_CELL_MOVE;
            }
            row--;
        }
    }
}

Cell *getRightCell(int row, int col) {
    while (++col < COLS) {
        if (grid[row][col] != NULL) return grid[row][col];
    }
    return NULL;
}

void moveLeft() {
    for (int row = 0; row < ROWS; row++) {
        int col = 0;
        while (col < COLS) {
            Cell *cell = grid[row][col];
            if (cell == NULL) {
                // 当前是空的 向右找一个不空的格子移动到这里
                Cell *rightCell = getRightCell(row, col);
                if (rightCell == NULL) break;

                // 新建个格子 放在当前位置 让右边的格子移动过来
                Cell *newCell = (Cell *)malloc(sizeof(Cell));
                newCell->num = 0;
                newCell->row = row;
                newCell->col = col;
                newCell->fromCells[0] = rightCell;
                grid[row][col] = newCell;

                grid[rightCell->row][rightCell->col] = NULL;
                rightCell->direction = MOVE_DIRECTION_LEFT;
                rightCell->pos = getPosOfRowCol(rightCell->row, rightCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                rightCell->moveSpeed = (rightCell->pos.x - dest.x) / MOVE_TIME;

                cell = newCell;

                gameState = GAME_STATE_CELL_MOVE;
            }

            Cell *rightCell = getRightCell(row, col);
            if (rightCell == NULL) break;

            // 右边的数字跟当前数字一样
            int cellNum = cell->num;
            if (cell->fromCells[0]) {
                cellNum += cell->fromCells[0]->num;
            }
            if (cell->fromCells[1]) {
                cellNum += cell->fromCells[1]->num;
            }
            if (cellNum == rightCell->num) {
                grid[rightCell->row][rightCell->col] = NULL;
                rightCell->direction = MOVE_DIRECTION_LEFT;
                rightCell->pos = getPosOfRowCol(rightCell->row, rightCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                rightCell->moveSpeed = (rightCell->pos.x - dest.x) / MOVE_TIME;
                if (cell->fromCells[0] == NULL) {
                    cell->fromCells[0] = rightCell;
                } else {
                    cell->fromCells[1] = rightCell;
                }
                gameState = GAME_STATE_CELL_MOVE;
            }
            col++;
        }
    }
}

Cell *getLeftCell(int row, int col) {
    while (--col >= 0) {
        if (grid[row][col] != NULL) return grid[row][col];
    }
    return NULL;
}

void moveRight() {
    for (int row = 0; row < ROWS; row++) {
        int col = COLS - 1;
        while (col >= 0) {
            Cell *cell = grid[row][col];
            if (cell == NULL) {
                // 当前是空的 向左找一个不空的格子移动到这里
                Cell *leftCell = getLeftCell(row, col);
                if (leftCell == NULL) break;

                // 新建个格子 放在当前位置 让下面的格子移动过来
                Cell *newCell = (Cell *)malloc(sizeof(Cell));
                newCell->num = 0;
                newCell->row = row;
                newCell->col = col;
                newCell->fromCells[0] = leftCell;
                grid[row][col] = newCell;

                grid[leftCell->row][leftCell->col] = NULL;
                leftCell->direction = MOVE_DIRECTION_RIGHT;
                leftCell->pos = getPosOfRowCol(leftCell->row, leftCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                leftCell->moveSpeed = (dest.x - leftCell->pos.x) / MOVE_TIME;

                cell = newCell;

                gameState = GAME_STATE_CELL_MOVE;
            }

            Cell *leftCell = getLeftCell(row, col);
            if (leftCell == NULL) break;

            // 上面的数字跟当前数字一样
            int cellNum = cell->num;
            if (cell->fromCells[0]) {
                cellNum += cell->fromCells[0]->num;
            }
            if (cell->fromCells[1]) {
                cellNum += cell->fromCells[1]->num;
            }
            if (cellNum == leftCell->num) {
                grid[leftCell->row][leftCell->col] = NULL;
                leftCell->direction = MOVE_DIRECTION_RIGHT;
                leftCell->pos = getPosOfRowCol(leftCell->row, leftCell->col);
                Vector2 dest = getPosOfRowCol(row, col);
                leftCell->moveSpeed = (dest.x - leftCell->pos.x) / MOVE_TIME;
                if (cell->fromCells[0] == NULL) {
                    cell->fromCells[0] = leftCell;
                } else {
                    cell->fromCells[1] = leftCell;
                }

                gameState = GAME_STATE_CELL_MOVE;
            }

            col--;
        }
    }
}
